﻿//////////////////////////////////////////////////////////////////
//
// Planet.cs
//
// Copyright (c) 2012 Dan Pike.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using Factory;
using Interfaces;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Drawing.Imaging;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Timers;
using Utilities;
using Wintellect.PowerCollections;

namespace Universe
{
   internal class Planet : IPlanet
   {
      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      
      private static Logger logger_;
      private string displayName_;
      private IPlanetFact planetFact_;
      private Timer timer_;
      private ILocation homeAddress_;
      private ITerrain terrain_;
      private PriorityQueue<PlanetCell, long> actionCells_;

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// The root object on which all entities "reside"
      /// </summary>
      public Planet()
      {
         logger_ = Program.Logger;
         actionCells_ = new PriorityQueue<PlanetCell, long>((x, y) => Math.Sign(x - y));
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Implementation of IPlanet property
      /// </summary>
      public string DisplayName
      {
         get { return displayName_; }
         set { displayName_ = value; }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Implementation of IPlanet property
      /// </summary>
      public IPlanetFact Fact
      {
         get { return planetFact_; }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Implementation of IPlanet property
      /// </summary>
      public ITerrain Terrain
      {
         get { return terrain_; }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Implementation of IPlanet
      /// </summary>
      public bool Initialize(IPlanetFact planetFact, string displayName)
      {
         try
         {
            displayName_ = displayName;
            planetFact_ = planetFact;
            homeAddress_ = planetFact_.HomeAddress;

            // Build the ecosystem for the tile that contains the home address
            terrain_ = Program.Loader.Create<ITerrain>();
            short[,] heights = terrain_.LoadTile(homeAddress_);

            DateTime now = DateTime.Now;
            BuildCellNormals(now, heights);

            return true;
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      ///
      /// </summary>
      private void BuildCellNormals(DateTime now, short[,] heights)
      {
         DateTime afterThreeSeconds = now + new TimeSpan(0, 0, 3);
         for (int row = Const.tileWidth60; 0 <= row; --row)
         {
            for (int column = 0; Const.tileWidth61 > column; ++column)
            {
               // Work out the climate for this cell
               Tuple<Vector3, short> result = GetNormal(heights, column, row);
               Vector3 cellNormal = result.Item1;
               short averageHeight = result.Item2;

               // Flat & low stuff can grow trees, so we enable cells for these
               bool isAutoCell = PlanetCell.CanGrowTrees(cellNormal, heights[row, column]);

               if (isAutoCell)
               {
                  // This location may do something, so we create a PlanetCell object for it
                  PlanetCell cell = new PlanetCell();
                  cell.X = column;
                  cell.Y = row;
                  cell.Normal = cellNormal;
                  cell.Elevation = averageHeight;
                  cell.Content = CellContent.Nature;

                  // Queue this cell for further action after three seconds
                  lock (actionCells_)
                  {
                     actionCells_.SetPriority(cell, afterThreeSeconds.Ticks);
                  }
               }
            }
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// What direction is this cell facing?
      /// </summary>
      private Tuple<Vector3, short> GetNormal(short[,] heights, int column, int row)
      {
         Vector3[] v = new Vector3[]
            {
               new Vector3(column, row, heights[row, column]),
               new Vector3(column + 1, row, heights[row, column]),
               new Vector3(column + 1, row + 1, heights[row, column]),
               new Vector3(column, row + 1, heights[row, column]),
            };
         Vector3[] e = new Vector3[]
            {
               v[1] - v[0],
               v[2] - v[1],
               v[3] - v[2],
               v[0] - v[3],
            };
         Vector3 normal
            = Vector3.Cross(e[0], -e[3])
            + Vector3.Cross(e[1], -e[0])
            + Vector3.Cross(e[2], -e[1])
            + Vector3.Cross(e[3], -e[2])
            ;
         normal.Normalize();
         return Tuple.Create(normal, (short)Math.Round((v[0].Z + v[1].Z + v[2].Z + v[3].Z) / 4.0f));
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Implementation of IPlanet
      /// </summary>
      public void Run()
      {
         try
         {
            // Start our timer to process active planet cells
            timer_ = new Timer();
            timer_.AutoReset = true;
            timer_.Elapsed += new ElapsedEventHandler(HandleElapsed);
            timer_.Interval = 1000.0f;
            timer_.Enabled = true;
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      void HandleElapsed(object sender, ElapsedEventArgs e)
      {
         try
         {
            // Get a list of all of the cells that need work
            long tickNow = e.SignalTime.Ticks;

            List<PlanetCell> cells = new List<PlanetCell>();
            lock (actionCells_)
            {
               while ((0 < actionCells_.Count) && (actionCells_.PeekNextPriority() < tickNow))
               {
                  cells.Add(actionCells_.RemoveNext());
               }
            }

            // Run all of the cells that have timed out
            cells.ForEach(cell => cell.Run(this));
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }
   }
}
